/*-
 * Copyright (c) 2012-2013 Shang-Rong Cai. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $ MTBLAS: isa_op4.c    Rev. 0.0.18    2013-10-22T15:45+08:00 $
 */

#include <stdio.h>

#include <math.h>
#include <complex.h>

#include <exec_isa.h>

/* Print Out Meta of Operands */
void op4_meta_op(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    printf("OPERAND: ra ADDRESS: %p, STEP: %zd.\n", ra->addr, ra->step);
    printf("OPERAND: rb ADDRESS: %p, STEP: %zd.\n", rb->addr, rb->step);
    printf("OPERAND: rc ADDRESS: %p, STEP: %zd.\n", rc->addr, rc->step);
    printf("OPERAND: rd ADDRESS: %p, STEP: %zd.\n", rd->addr, rd->step);

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


/* Combine execute() and opcode */
/*
 * ra: opchunk_t; target address of execution: opcode, counts, operands.
 * rb: opchunk_t; for modifying target, update target before executing ra.
 * rc: opchunk_t; set/reset address value (change after run)
 * rd: opchunk_t; set/reset step value (change after run)
 *
 * Remark: 
 * The modifying target will be update from the outer to inner when using this 
 * opocde nestedly.  The outer will update before running inner.
 */
void op4_exec_op(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    addr_t oc;
    size_t sz;

    operand_t op_a;
    operand_t op_b;
    operand_t op_c;
    operand_t op_d;

    opchunk_t *pk_a;
    opchunk_t *pk_b;
    opchunk_t *pk_c;
    opchunk_t *pk_d;

    pk_a = (opchunk_t*) ra->addr;
    pk_b = (opchunk_t*) rb->addr;
    pk_c = (opchunk_t*) rc->addr;
    pk_d = (opchunk_t*) rd->addr;

    /* Modifying Target */
    pk_b->oc = pk_c->oc;
    pk_b->sz = pk_c->sz;

    pk_b->ra.addr = pk_c->ra.addr;
    pk_b->ra.step = *((size_t*) pk_d->ra.addr);
    pk_b->rb.addr = pk_c->rb.addr;
    pk_b->rb.step = *((size_t*) pk_d->rb.addr);
    pk_b->rc.addr = pk_c->rc.addr;
    pk_b->rc.step = *((size_t*) pk_d->rc.addr);
    pk_b->rd.addr = pk_c->rd.addr;
    pk_b->rd.step = *((size_t*) pk_d->rd.addr);

    /* Setup Target Run */
    oc = pk_a->oc;
    sz = pk_a->sz;

    op_a.addr = pk_a->ra.addr;
    op_a.step = pk_a->ra.step;
    op_b.addr = pk_a->rb.addr;
    op_b.step = pk_a->rb.step;
    op_c.addr = pk_a->rc.addr;
    op_c.step = pk_a->rc.step;
    op_d.addr = pk_a->rd.addr;
    op_d.step = pk_a->rd.step;

    execute(oc, sz, &op_a, &op_b, &op_c, &op_d);

    /* Update Modifiers Address */
    pk_c->ra.addr += pk_c->ra.step;
    pk_c->rb.addr += pk_c->rb.step;
    pk_c->rc.addr += pk_c->rc.step;
    pk_c->rd.addr += pk_c->rd.step;

    /* Update Modifiers Step */
    pk_d->ra.addr += pk_d->ra.step;
    pk_d->rb.addr += pk_d->rb.step;
    pk_d->rc.addr += pk_d->rc.step;
    pk_d->rd.addr += pk_d->rd.step;

    /* Update Operands Address */
    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


/* %d = %a * %b + %c */
void op4_fma_ff(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float *rg_a;
    float *rg_b;
    float *rg_c;
    float *rg_d;

    rg_a = (float*) ra->addr;
    rg_b = (float*) rb->addr;
    rg_c = (float*) rc->addr;
    rg_d = (float*) rd->addr;

    *rg_d = *rg_a * *rg_b + *rg_c;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


void op4_fma_df(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double *rg_a;
    double *rg_b;
    double *rg_c;
    double *rg_d;

    rg_a = (double*) ra->addr;
    rg_b = (double*) rb->addr;
    rg_c = (double*) rc->addr;
    rg_d = (double*) rd->addr;

    *rg_d = *rg_a * *rg_b + *rg_c;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


void op4_fma_lf(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double *rg_a;
    long double *rg_b;
    long double *rg_c;
    long double *rg_d;

    rg_a = (long double*) ra->addr;
    rg_b = (long double*) rb->addr;
    rg_c = (long double*) rc->addr;
    rg_d = (long double*) rd->addr;

    *rg_d = *rg_a * *rg_b + *rg_c;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


void op4_fma_fc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float complex *rg_a;
    float complex *rg_b;
    float complex *rg_c;
    float complex *rg_d;

    rg_a = (float complex*) ra->addr;
    rg_b = (float complex*) rb->addr;
    rg_c = (float complex*) rc->addr;
    rg_d = (float complex*) rd->addr;

    *rg_d = *rg_a * *rg_b + *rg_c;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


void op4_fma_dc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double complex *rg_a;
    double complex *rg_b;
    double complex *rg_c;
    double complex *rg_d;

    rg_a = (double complex*) ra->addr;
    rg_b = (double complex*) rb->addr;
    rg_c = (double complex*) rc->addr;
    rg_d = (double complex*) rd->addr;

    *rg_d = *rg_a * *rg_b + *rg_c;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


void op4_fma_lc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double complex *rg_a;
    long double complex *rg_b;
    long double complex *rg_c;
    long double complex *rg_d;

    rg_a = (long double complex*) ra->addr;
    rg_b = (long double complex*) rb->addr;
    rg_c = (long double complex*) rc->addr;
    rg_d = (long double complex*) rd->addr;

    *rg_d = *rg_a * *rg_b + *rg_c;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


/* %d = conj(%a) * %b + %c */
void op4_conjfma_fc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    float complex *rg_a;
    float complex *rg_b;
    float complex *rg_c;
    float complex *rg_d;

    rg_a = (float complex*) ra->addr;
    rg_b = (float complex*) rb->addr;
    rg_c = (float complex*) rc->addr;
    rg_d = (float complex*) rd->addr;

    *rg_d = conjf(*rg_a) * *rg_b + *rg_c;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


void op4_conjfma_dc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    double complex *rg_a;
    double complex *rg_b;
    double complex *rg_c;
    double complex *rg_d;

    rg_a = (double complex*) ra->addr;
    rg_b = (double complex*) rb->addr;
    rg_c = (double complex*) rc->addr;
    rg_d = (double complex*) rd->addr;

    *rg_d = conj(*rg_a) * *rg_b + *rg_c;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}


void op4_conjfma_lc(operand_t *ra, operand_t *rb, operand_t *rc, operand_t *rd)
{
    long double complex *rg_a;
    long double complex *rg_b;
    long double complex *rg_c;
    long double complex *rg_d;

    rg_a = (long double complex*) ra->addr;
    rg_b = (long double complex*) rb->addr;
    rg_c = (long double complex*) rc->addr;
    rg_d = (long double complex*) rd->addr;

    *rg_d = conjl(*rg_a) * *rg_b + *rg_c;

    ra->addr += ra->step;
    rb->addr += rb->step;
    rc->addr += rc->step;
    rd->addr += rd->step;
}
